#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <string>
#include <sys/syscall.h>
#include <sys/types.h>
#include <thread>
#include <unistd.h>

#include <pybind11/embed.h>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

class MessageQueue {
 public:
  using Callback = std::function<void(const std::string&)>;

 public:
  MessageQueue() {
    auto default_callback = [](const std::string&) {};

    cpp_callback_ = default_callback;
    py_callback_ = default_callback;
  }

  void SetPythonCallback(Callback callback) { py_callback_ = callback; }

  void SetCppCallback(Callback callback) { cpp_callback_ = callback; }

  void WriteMessageToCpp(const std::string& data) {
    std::unique_lock<std::mutex> mlock(mutex_);
    cpp_callback_(data);
  }

  void WriteMessageToPython(const std::string& data) {
    pybind11::gil_scoped_acquire gil;
    py_callback_(data);
  }

 private:
  Callback cpp_callback_;
  Callback py_callback_;
  std::mutex mutex_;
};

// Make it thread-safe
MessageQueue* GetMessageQueue() {
  static auto queue = std::make_unique<MessageQueue>();
  return queue.get();
}

PYBIND11_EMBEDDED_MODULE(message_queue, m) {
  m.def("set_python_callback", [](MessageQueue::Callback callback) {
    GetMessageQueue()->SetPythonCallback(callback);
  });

  m.def("write_message", [](const std::string& msg) {
    GetMessageQueue()->WriteMessageToCpp(msg);
  });

  m.def("get_thread_id", []() { return syscall(__NR_gettid); });
};

void cpp(const std::string data) {
  std::cout << "received data from python: " << data << std::endl;
}

void start_python() {
  pybind11::scoped_interpreter guard{};
  pybind11::module_::import("pymod");
}

void start_cpp() {
  usleep(100000);
  GetMessageQueue()->SetCppCallback(&cpp);

  for (int n = 0; n < 10; n++) {
    std::cout << "send msg in cpp! " << n << std::endl;
    GetMessageQueue()->WriteMessageToPython(
        "hello world, this is from cpp: " + std::to_string(n));
  }
}

int main() {
  GetMessageQueue()->SetCppCallback(&cpp);
  std::thread python_thread(start_python);

  start_cpp();
  python_thread.join();
  return 0;
}
